package com.hello.sandbox.utils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

/** Created by BlackBox on 2022/3/21. */
public class ReflectionClass {
  @SuppressWarnings("rawtypes")
  public static void print(String name) {
    try {
      // print class name and super class name (if != object)
      Class cl = Class.forName(name);
      Class supercl = cl.getSuperclass();
      Class[] interfaces = cl.getInterfaces();
      String modifiers = Modifier.toString(cl.getModifiers());
      if (modifiers.length() > 0) {
        System.out.print(modifiers);
      }

      // is class or interface
      if (!cl.isInterface()) {
        System.out.print(" class ");
      }

      printType(cl);

      if (supercl != null && supercl != Object.class) {
        System.out.print(" extends ");
        printType(supercl);
      }

      if (interfaces.length > 0) {
        System.out.print(" implements ");

        for (int j = 0; j < interfaces.length; j++) {
          if (j > 0) {
            System.out.print(", ");
          }
          printType(interfaces[j]);
        }
      }

      System.out.print(" {\n");

      printConstructors(cl);
      System.out.println();
      printMethods(cl);
      System.out.println();
      printFields(cl);
      System.out.println("}");

      printExtendsChain(cl);
    } catch (ClassNotFoundException e) {
      e.printStackTrace();
    }
  }

  /**
   * Prints all constructors of a class
   *
   * @param cl a class
   */
  @SuppressWarnings("rawtypes")
  private static void printConstructors(Class cl) {
    Constructor[] constructors = cl.getDeclaredConstructors();

    for (Constructor c : constructors) {
      String name = c.getName();
      System.out.print("    ");
      // print modifiers.
      String modifiers = Modifier.toString(c.getModifiers());
      if (modifiers.length() > 0) {
        System.out.print(modifiers + " ");
      }

      // print name and parameter types.
      System.out.print(name.substring(name.lastIndexOf(".") + 1) + " (");
      Class[] paramTypes = c.getParameterTypes();
      for (int j = 0; j < paramTypes.length; j++) {
        if (j > 0) {
          System.out.print(", ");
        }
        printType(paramTypes[j]);
      }
      System.out.print(")");

      // print exceptions
      Class[] exceptions = c.getExceptionTypes();
      if (exceptions.length > 0) {
        System.out.print(" throws ");
        for (int j = 0; j < exceptions.length; j++) {
          if (j > 0) {
            System.out.print(", ");
          }
          printType(exceptions[j]);
        }
      }

      System.out.println(";");
    }
  }

  /**
   * Prints all methods of a class
   *
   * @param cl a class
   */
  @SuppressWarnings("rawtypes")
  private static void printMethods(Class cl) {
    Method[] methods = cl.getDeclaredMethods();
    for (Method m : methods) {
      Class retType = m.getReturnType();
      String name = m.getName();

      System.out.print("    ");
      // print modifiers, return type and method name
      String modifiers = Modifier.toString(m.getModifiers());
      if (modifiers.length() > 0) {
        System.out.print(modifiers + " ");
      }
      printType(retType);
      System.out.print(" " + name + "(");

      // print parameter types
      Class[] paramTypes = m.getParameterTypes();
      for (int j = 0; j < paramTypes.length; j++) {
        if (j > 0) {
          System.out.print(", ");
        }
        printType(paramTypes[j]);
      }

      System.out.print(")");

      // print exceptions
      Class[] exceptions = m.getExceptionTypes();
      if (exceptions.length > 0) {
        System.out.print(" throws ");
        for (int j = 0; j < exceptions.length; j++) {
          if (j > 0) {
            System.out.print(", ");
          }
          printType(exceptions[j]);
        }
      }

      System.out.println(";");
    }
  }

  /**
   * Prints all fields of a class
   *
   * @param cl a class
   */
  @SuppressWarnings("rawtypes")
  private static void printFields(Class cl) {
    Field[] fields = cl.getDeclaredFields();

    for (Field f : fields) {
      Class type = f.getType();
      String name = f.getName();
      System.out.print("    ");
      String modifiers = Modifier.toString(f.getModifiers());
      if (modifiers.length() > 0) {
        System.out.print(modifiers + " ");
      }
      printType(type);
      System.out.println(" " + name + ";");
    }
  }

  /**
   * Prints a type.
   *
   * @param type a type.
   */
  @SuppressWarnings("rawtypes")
  private static void printType(Class type) {
    String name = type.getName();
    if (name.startsWith("[")) {
      if (name.startsWith("[C")) {
        System.out.print("char[]");
      } else if (name.startsWith("[B")) {
        System.out.print("byte[]");
      } else if (name.startsWith("[S")) {
        System.out.print("short[]");
      } else if (name.startsWith("[I")) {
        System.out.print("int[]");
      } else if (name.startsWith("[J")) {
        System.out.print("long[]");
      } else if (name.startsWith("[F")) {
        System.out.print("float[]");
      } else if (name.startsWith("[D")) {
        System.out.print("double[]");
      } else if (name.startsWith("[Z")) {
        System.out.print("boolean[]");
      } else if (name.startsWith("[L")) {
        System.out.print(name.substring(name.lastIndexOf(".") + 1, name.length() - 1) + "[]");
      }
    } else {
      System.out.print(name.substring(name.lastIndexOf(".") + 1));
    }
  }

  /**
   * Prints the extends chain for a class
   *
   * @param cl a class
   */
  @SuppressWarnings("rawtypes")
  public static void printExtendsChain(Class cl) {
    System.out.print("\n\nExtends chain:\n    ");
    printType(cl);
    Class superclass = cl.getSuperclass();
    while (superclass != Object.class) {
      System.out.print(" --> ");
      printType(superclass);
      superclass = superclass.getSuperclass();
    }
    System.out.print(" --> ");
    printType(superclass);
  }
}
